feat: add Elysia adapter#46
Conversation
commit: |
396ca1a to
2633af3
Compare
| class SupabaseAuthError extends Error { | ||
| status: number | ||
| constructor(message: string, status: number, cause: unknown) { | ||
| super(message, { cause }) | ||
| this.status = status | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Good call! Renamed to SupabaseError.
The intent behind a wrapper class rather than throwing AuthError directly is to give Elysia consumers a single, stable discriminant in onError regardless of whether the failure was an auth error or an env misconfiguration.
There was a problem hiding this comment.
Uhmm so what about registering the original error classes instead of wrapping it?
new Elysia()
.error({
EnvError,
AuthError,
})There was a problem hiding this comment.
Good point! I tried throwing AuthError directly with .error({ AuthError }), but Elysia's error registry inside a scoped plugin doesn't propagate to the parent's onError at runtime, so code === 'AuthError' silently fails even though TS infers it correctly.
We could keep a thin SupabaseError wrapper but expose .code and .status directly on it (no .cause indirection). That way code discrimination still works idiomatically. Open to other suggestions!
There was a problem hiding this comment.
So if we pass the AuthError directly, one will check it in the elysia error hook like this:
import { AuthError } from '@supabase/server/errors'
app.onError(({ error, status }) => {
if (!(error instanceof AuthError)) return
return status(error.status as 401, {
error: error.message,
code: error.code,
})
})but if we create a thin wrapper and expose .code and .status:
app.onError(({ code, error, status }) => {
if (code !== 'SupabaseError') return
return status(error.status as 401, {
error: error.message,
code: error.code,
})
})2nd is more idiomatic to Elysia, no extra import, and the code string is the standard discriminant. The first works but bypasses Elysia's custom error system entirely
There was a problem hiding this comment.
2nd is more idiomatic to Elysia
Sure! so keep the version you think better aligns to Elysia framework.
Mark it as "resolved" with your confirmation
|
Thanks for contributing, |
|
@kallebysantos thanks for the thorough review. Resolved the comments! ❤️ |
kallebysantos
left a comment
There was a problem hiding this comment.
LGTM!
Please fix the merge conflicts as well give me the confirmation about SupabaseError thing.
Then we should be good to merge!
b3a3438 to
1217b8d
Compare
Thanks for approving! Going with the thin |
# Conflicts: # README.md
Hi @wobsoriano! we are ready to merge this... can you confirm it this is already taken care of fully? |
I just pushed an update (ref) that gives clean access to Tested this locally in a simple Elysia app: .onError(({ code, error, status }) => {
if (code !== 'SupabaseError') return
return status(error.status {
error: error.message,
code: error.cause.code,
})
})sending a curl request {"error":"Invalid credentials","code":"INVALID_CREDENTIALS"}Ready to merge 👍🏼 |
|
One small suggestion before merge: would you consider hoisting Right now class SupabaseError extends Error {
status: number
code: string
declare cause: AuthError
constructor(inner: AuthError) {
super(inner.message, { cause: inner })
this.status = inner.status
this.code = inner.code
}
}Then the handler becomes uniform: .onError(({ code, error, status }) => {
if (code !== 'SupabaseError') return
return status(error.status as 401, {
error: error.message,
code: error.code,
})
})
|
|
Unfortunately it collides with an Elysia internal: when a thrown error has an own That's also why So if you have this in .onError(({ code, error }) => {
console.log('code value:', code)
console.log('code === SupabaseError:', code === 'SupabaseError')
if (code !== 'SupabaseError') return
// ...
})The output is: |
|
Got it! Thanks for the explanation. Going to merge this now, and create a new release! |
Resolves conflicts with the Elysia adapter (supabase#46) and the 1.1.0 release that landed on main while review feedback was being addressed. - README.md / src/adapters/README.md: list both Elysia and NestJS in the adapter tables; preserve the community-driven note added upstream. - package.json / jsr.json / tsdown.config.ts: combine Elysia + NestJS entries, peer deps, and bundle externals. Keep upstream's `@supabase/supabase-js` ^2.105.4 bump. - pnpm-workspace.yaml: set `allowBuilds` policy for `@nestjs/core` and `@swc/core` to `false` — they're dev-only deps with no native bindings we need to compile. - pnpm-lock.yaml: regenerated.
What kind of change does this PR introduce?
Feature
What is the current behavior?
@supabase/serverships framework adapters for Hono and H3 / Nuxt, but not for Elysia. Users building APIs with Elysia have to wire up Supabase auth manually with the core primitives.What is the new behavior?
Adds
@supabase/server/adapters/elysia, a first-class plugin adapter for Elysia. Elysia has been gaining significant traction in the TypeScript ecosystem, particularly in the Bun community, for its end-to-end type safety and expressive plugin API.Usage with a plain Elysia app:
Per-route auth using scoped groups:
Custom error handling via Elysia's
onError:Additional context
.resolve()hook with.as('scoped')so the context propagates to parent app routes.SupabaseAuthError, which integrates with Elysia's standardonErrorflow and preserves the originalAuthErroronerror.cause.Consumers discriminate oncode === 'SupabaseAuthError'inonErrorwithout needing to import the class.